package br.com.hs.nfe.business;

import br.com.hs.nfe.AppUtil;
import br.com.hs.nfe.NotaFiscalHelper;
import br.com.hs.nfe.certificado.Certificado;
import br.com.hs.nfe.certificado.exception.HSCertificadoException;
import br.com.hs.nfe.common.anotation.Node;
import br.com.hs.nfe.common.enumeration.*;
import br.com.hs.nfe.common.exception.HSBaseException;
import br.com.hs.nfe.common.exception.HSCommonException;
import br.com.hs.nfe.common.to.*;
import br.com.hs.nfe.common.util.*;
import br.com.hs.nfe.common.validation.NotaFiscalValidation;
import br.com.hs.nfe.common.validation.ValidadorAnotacoes;
import br.com.hs.nfe.common.validation.exception.HSValidationException;
import br.com.hs.nfe.util.EstadoTO;
import br.com.hs.nfe.util.UtilHelper;
import br.com.hs.nfe.ws.exception.HSConnectionClientException;
import br.com.hs.nfe.ws.exception.HSConnectionServerException;
import br.com.hs.nfe.ws.exception.HSProxyException;
import br.com.hs.nfe.ws.exception.HSSocketException;
import br.com.hs.nfe.ws.exception.HSTimeoutException;
import br.com.hs.nfe.ws.exception.HSWebServiceException;
import br.inf.portalfiscal.nfe.NFeDocument;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;

/**
 * Classe responsável pelas regras de negócio da NF-e.
 * @author Ranlive Hrysyk
 */
public class NotaFiscalBusiness {

    private WSBusiness wsBusiness;

    /**
     * Obtém a instância da clase WSBusiness
     */
    private WSBusiness getWSBusiness() {
        if (wsBusiness == null) {
            wsBusiness = new WSBusiness();
        }
        return wsBusiness;
    }

    /**
     * Assina o XML da NotaFiscalTO.
     */
    public void assinar(NotaFiscalTO notaFiscalTO, CertificadoTO certificadoTO) throws HSCertificadoException {
        if (validarMaximoBytes(notaFiscalTO)) {
            String referenceURI = HSConstants.ID_NFE + ChaveAcessoHelper.gerarChaveAcesso(notaFiscalTO);
            String xmlAssinado = Certificado.assinarXML(certificadoTO.getX509Certificate(), certificadoTO.getPrivateKey(), referenceURI, notaFiscalTO.getXmlString(), notaFiscalTO.getEmitenteTO().getCNPJ());
            notaFiscalTO.setXmlString(xmlAssinado);
            notaFiscalTO.setSituacao(SituacaoNFeEnum.ASSINADA);
            notaFiscalTO.addMessage(HSMessageConstants.MSG_NOTA_FISCAL_ASSINADA_COM_SUCESSO);
        }
    }

    /**
     * Valida o tamanho máximo do XML.
     */
    private boolean validarMaximoBytes(NotaFiscalTO notaFiscalTO) {
        if (!NotaFiscalValidation.getInstance().validarMaximoBytes(notaFiscalTO.getXmlString())) {
            notaFiscalTO.addError(HSMessageConstants.MSG_NOTA_FISCAL_MAXIMO_BYTES);
            return false;
        }
        return true;
    }

    /**
     * Monta o nome da classe na mensagem de erro.
     */
    private String createClassName(BaseTO baseTO) {
        Node node = baseTO.getClass().getAnnotation(Node.class);
        if (node != null) {
            return MessageFormat.format(HSConstants.APP_CLASS_NAME, new Object[]{node.name()});
        }
        return null;
    }

    /**
     * Valida as regras de negócio de um cliente.
     * @param clienteTO Cliente a ser validado.
     */
    public void validarClienteTO(ClienteTO clienteTO) throws HSValidationException {
        clienteTO.clearValidationError();
        String className = createClassName(clienteTO);

        if ((StringHelper.isBlankOrNull(clienteTO.getCPF())) && (StringHelper.isBlankOrNull(clienteTO.getCNPJ()))) {
            clienteTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_OBRIGATORIO, new Object[]{HSConstants.XML_CNPJ_CPF, className}));
        } else if ((!StringHelper.isBlankOrNull(clienteTO.getCPF())) && (!StringHelper.isBlankOrNull(clienteTO.getCNPJ()))) {
            clienteTO.addValidationError(MessageFormat.format(HSMessageConstants.MSG_CHOICE_CAMPO, new Object[]{HSConstants.XML_CAMPO_CNPJ, HSConstants.XML_CAMPO_CPF, className}));
        }

        ValidadorAnotacoes.validate(clienteTO);
    }

    /**
     * Valida as regras de negócio de um emitente.
     * @param emitenteTO Emitente a ser validado.
     */
    public void validarEmitenteTO(EmitenteTO emitenteTO) throws HSValidationException {
        emitenteTO.clearValidationError();
        String className = createClassName(emitenteTO);

        if ((StringHelper.isBlankOrNull(emitenteTO.getCPF())) && (StringHelper.isBlankOrNull(emitenteTO.getCNPJ()))) {
            emitenteTO.addError(MessageFormat.format(HSMessageConstants.MSG_CAMPO_OBRIGATORIO, new Object[]{HSConstants.XML_CNPJ_CPF, className}));
        } else if ((!StringHelper.isBlankOrNull(emitenteTO.getCPF())) && (!StringHelper.isBlankOrNull(emitenteTO.getCNPJ()))) {
            emitenteTO.addError(MessageFormat.format(HSMessageConstants.MSG_CHOICE_CAMPO, new Object[]{HSConstants.XML_CAMPO_CNPJ, HSConstants.XML_CAMPO_CPF, className}));
        }

        ValidadorAnotacoes.validate(emitenteTO);
    }

    /**
     * Valida as regras de negócio de um produto.
     * @param produtoTO Produto a ser validado.
     */
    public void validarProdutoTO(ProdutoTO produtoTO) throws HSValidationException {
        produtoTO.clearValidationError();
        ValidadorAnotacoes.validate(produtoTO);
    }

    /**
     * Valida os dados da nota fiscal.
     * @param notaFiscalTO Nota fiscal a ser validado.
     */
    public void validarNotaFiscalTO(NotaFiscalTO notaFiscalTO) throws HSValidationException {
        try {
            //Limpa as mensagens de validação
            notaFiscalTO.clearValidationError();
            //Atualiza o xml da nota fiscal
            DocumentHelper.updateXml(notaFiscalTO);
            //Obtém o xml
            NFeDocument nfeDocument = DocumentHelper.createNFeDocument(notaFiscalTO.getXmlString());

            boolean codigoNumericoGeradoAutomaticamente = false;
            if ((StringHelper.isBlankOrNull(notaFiscalTO.getCodigoNumericoChaveAcesso())) || (notaFiscalTO.getCodigoNumericoChaveAcesso().length() != 8)) {
                notaFiscalTO.setCodigoNumericoChaveAcesso(ChaveAcessoHelper.gerarCodigoNumerico(notaFiscalTO.getXmlString()));
                codigoNumericoGeradoAutomaticamente = true;
            }

            //Atribui a chave de acesso
            notaFiscalTO.setDigitoVerificador(ChaveAcessoHelper.gerarDigitoChaveAcesso(notaFiscalTO));
            nfeDocument.getNFe().getInfNFe().getIde().setCNF(notaFiscalTO.getCodigoNumericoChaveAcesso());
            nfeDocument.getNFe().getInfNFe().getIde().setCDV(notaFiscalTO.getDigitoVerificador());
            nfeDocument.getNFe().getInfNFe().setId(HSConstants.ID_NFE + ChaveAcessoHelper.gerarChaveAcesso(notaFiscalTO));
            notaFiscalTO.setXmlString(DocumentHelper.createDocumentString(nfeDocument, true));

            //Calcula  e atribui o total da nota
            boolean totalNotaCalculadoIgualInformado = validarTotalNFe(notaFiscalTO);

            //Obtém o novo XML
            nfeDocument = DocumentHelper.createNFeDocument(notaFiscalTO.getXmlString());
            //Valida as regras de negócio da NF-e.
            NotaFiscalValidation.getInstance().validarDocumentoXML(notaFiscalTO, nfeDocument);
            //Valida o schema XML
            if (!notaFiscalTO.hasValidationError()) {
                NotaFiscalValidation.getInstance().executarValidacaoXMLBeans(notaFiscalTO, nfeDocument);
            }

            //Adiciona o erro de validação da nota.
            if (notaFiscalTO.hasValidationError()) {
                notaFiscalTO.addError(MessageFormat.format(HSMessageConstants.NFE_ANO_SERIE_NUMERO_INVALIDA, new Object[]{DateHelper.formatarDataHora(notaFiscalTO.getDataEmissao(), DateHelper.DATE_YEAR_X4_FORMAT), notaFiscalTO.getSerie(), notaFiscalTO.getNumero()}));
                if (codigoNumericoGeradoAutomaticamente) {
                    notaFiscalTO.setCodigoNumericoChaveAcesso(null);
                    notaFiscalTO.setDigitoVerificador(null);
                    DocumentHelper.excluirCodigoNumerico(notaFiscalTO);
                }
                return;
            }

            //Verifica se o total informa do é iqual ao calculado
            if (!totalNotaCalculadoIgualInformado) {
                return;
            }

            //Remove o namespace da declaração XML
            nfeDocument = DocumentHelper.createNFeDocument(notaFiscalTO.getXmlString());
            DocumentHelper.removerNamespaceDeclaration(nfeDocument, "xsi");
            notaFiscalTO.setXmlString(DocumentHelper.createDocumentString(nfeDocument, true));

            //Adiciona a mensagem de nota fiscal validada
            if (!notaFiscalTO.hasErrors()) {
                notaFiscalTO.setSituacao(SituacaoNFeEnum.VALIDADA);
                notaFiscalTO.addMessage(MessageFormat.format(HSMessageConstants.NOTA_FISCAL_VALIDADA_COM_SUCESSO, new Object[]{SituacaoNFeEnum.VALIDADA.getDescricao()}));
            }
        } catch (Exception ex) {
            throw new HSValidationException(ex.getMessage(), ex);
        }
    }

    /**
     * Valida o valor total da Nota Fiscal.
     */
    private boolean validarTotalNFe(NotaFiscalTO notaFiscalTO) throws HSValidationException {
        try {
            //Obtém o valor original
            String valorTotalOriginal = XMLHelper.getFirstTagConteudo(notaFiscalTO.getXmlString(), "vNF", false, false);
            NotaFiscalTO notaFiscalTOClone = (NotaFiscalTO) notaFiscalTO.clone();

            //Calcula o total da nota
            NotaFiscalHelper notaFiscalHelper = new NotaFiscalHelper();
            notaFiscalHelper.calculateTotalNotaFiscal(notaFiscalTOClone);
            if (StringHelper.isBlankOrNull(valorTotalOriginal)) {
                return true;
            }
            String valorTotalCalculado = XMLHelper.getFirstTagConteudo(notaFiscalTOClone.getXmlString(), "vNF", false, false);

            //Compara os valores da nota
            if (!valorTotalCalculado.equals(valorTotalOriginal)) {
                notaFiscalTO.addValidationError(MessageFormat.format(HSMessageConstants.NOTA_FISCAL_TOTAL_INCONSISTENTE, new Object[]{StringHelper.formatarValor(valorTotalOriginal, 2, 2), StringHelper.formatarValor(valorTotalCalculado, 2, 2)}));
                return false;
            }
            return true;
        } catch (Exception e) {
            throw new HSValidationException(e.getMessage(), e);
        }
    }

    /**
     * Consulta o status do serviço case não haja exceção o serviço está OK.
     */
    private WSStatusRetornoEnum consultarStatusServico(EstadoTO emitenteEstadoTO, CertificadoTO certificadoTO, boolean isSefaz) throws HSWebServiceException, HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException {
        EstadoTO estadoTO = ScanHelper.getEstadoTO(emitenteEstadoTO, isSefaz);
        String url;
        try {
            url = UtilHelper.getInstance().getURLByUF(emitenteEstadoTO.getSigla(), ServicoSefazEnum.StatusServico);
            if (StringHelper.isBlankOrNull(url)) {
                throw new HSWebServiceException(MessageFormat.format(HSMessageConstants.MSG_UF_NAO_HABILITADA, new Object[]{estadoTO.getSigla()}));
            }
        } catch (Exception e) {
            throw new HSWebServiceException(MessageFormat.format(HSMessageConstants.MSG_UF_NAO_HABILITADA, new Object[]{estadoTO.getSigla()}));
        }

        String consultaStatusServicoRetorno;
        try {
            consultaStatusServicoRetorno = getWSBusiness().servico(DocumentHelper.createConsStatServDocument(emitenteEstadoTO.getCodigo()), estadoTO, ServicoSefazEnum.StatusServico, certificadoTO);
        } catch (UnsupportedEncodingException ex) {
            throw new HSWebServiceException(ex.getMessage(), ex);
        } catch (HSCommonException ex) {
            throw new HSWebServiceException(ex.getMessage(), ex);
        }
        return DocumentHelper.validateRetConsStatServDocument(consultaStatusServicoRetorno);
    }

    /**
     * Lança uma exceção com o código do erro e e motivo.
     */
    private void lancarExceptionWebService(boolean isSefaz, String xmlRetorno) throws HSWebServiceException {
        String wsReceptor = isSefaz ? HSMessageConstants.MSG_WS_SEFAZ : HSMessageConstants.MSG_WS_RFB;
        String mensagemErro = MessageFormat.format(HSMessageConstants.MSG_WS_RETORNO_NOK, new Object[]{wsReceptor, XMLHelper.getTagConteudo(xmlRetorno, "cStat", false).get(0), XMLHelper.getTagConteudo(xmlRetorno, "xMotivo", false).get(0)});
        throw new HSWebServiceException(mensagemErro);
    }

    /**
     * Atribui a nova situação das NF-e's.
     */
    private void atualizarStatus(List<NotaFiscalTO> notas, SituacaoNFeEnum situacao) {
        for (NotaFiscalTO notaFiscalTO : notas) {
            notaFiscalTO.setSituacao(situacao);
        }
    }

    /**
     * Envia uma NF-e para a Sefaz
     * <p><b>IMPORTANTE: Somente deverão constar no lote, NF-e's de mesmo tipo de emissão e da mesma versão.</b></p>
     * @param loteTO Lote de transmissão
     * @param certificadoTO Certificado de autenticação
     * @throws HSWebServiceException Exceções do Web Service
     * @throws HSConnectionClientException Exceções na conexão do cliente
     * @throws HSConnectionServerException Exceções na conexão do servidor
     * @throws HSTimeoutException Exceções de tempo esgotado
     * @throws HSProxyException Exceções de proxy
     * @throws HSSocketException Exceções de conexão
     */
    public void transmitir(LoteTO loteTO, CertificadoTO certificadoTO) throws HSWebServiceException, HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException {
        boolean isSefaz = ScanHelper.isSefaz(loteTO);
        transmitir(loteTO, certificadoTO, isSefaz);
    }

    /**
     * Transmite uma nota fiscal à sefaz.
     */
    private void transmitir(LoteTO loteTO, CertificadoTO certificadoTO, boolean isSefaz) throws HSWebServiceException, HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException {
        EmitenteTO emitenteTO = AppUtil.getEmitenteTO();
        try {
            EstadoTO emitenteEstadoTO = UtilHelper.getInstance().getEstadoTOBySigla(emitenteTO.getEnderEmit().getUF());
            EstadoTO estadoTO = ScanHelper.getEstadoTO(emitenteEstadoTO, isSefaz);

            WSStatusRetornoEnum statusRetorno = consultarStatusServico(emitenteEstadoTO, certificadoTO, isSefaz);

            if ((!statusRetorno.equals(WSStatusRetornoEnum.STATUS_SERVICO_EM_OPERACAO))) {
                throw new HSConnectionClientException(MessageFormat.format(HSMessageConstants.MSG_WS_CONEXAO_INEXISTENTE_CLIENT, new Object[]{HSMessageConstants.MSG_WS_SEFAZ}));
            }

            //Limpa os erros das NF-e
            for (NotaFiscalTO notaFiscalTO : loteTO.getNotasFiscais()) {
                notaFiscalTO.setCodigoErro(null);
                notaFiscalTO.setMensagemErro(null);
            }

            //Atribui os dados ao lote
            loteTO.setCNPJTransmissao(!StringHelper.isBlankOrNull(emitenteTO.getCNPJ()) ? emitenteTO.getCNPJ() : emitenteTO.getCPF());
            loteTO.setDataTransmissao(new Date());

            //Cria o XML de envio
            String xmlEnvio = DocumentHelper.createEnviNFeDocument(loteTO);
            String retornoXML = getWSBusiness().servico(xmlEnvio, estadoTO, ServicoSefazEnum.Recepcao, certificadoTO);
            WSStatusRetornoEnum retornoEnum = DocumentHelper.validateRetEnviNFeDocument(retornoXML, loteTO);

            //Atribui os novos status            
            switch (retornoEnum.ordinal()) {
                case 1:
                    loteTO.setDataProcessamento(new Date());
                    loteTO.setXmlRetorno(retornoXML);
                    atualizarStatus(loteTO.getNotasFiscais(), SituacaoNFeEnum.TRANSMITIDA_COM_PENDENCIA);
                    break;
                case 2:
                    loteTO.setDataProcessamento(new Date());
                    loteTO.setXmlRetorno(retornoXML);
                    atualizarStatus(loteTO.getNotasFiscais(), SituacaoNFeEnum.REJEITADA);
                    break;
                default:
                    atualizarStatus(loteTO.getNotasFiscais(), SituacaoNFeEnum.EM_PROCESSAMENTO_SEFAZ);
            }
        } catch (HSConnectionClientException e) {
            throw new HSConnectionClientException(MessageFormat.format(HSMessageConstants.MSG_WS_CONEXAO_INEXISTENTE_CLIENT, new Object[]{HSMessageConstants.MSG_WS_SEFAZ}));
        } catch (HSConnectionServerException e) {
            throw new HSConnectionServerException(MessageFormat.format(HSMessageConstants.MSG_WS_CONEXAO_INEXISTENTE_SERVER, new Object[]{HSMessageConstants.MSG_WS_SEFAZ}));
        } catch (HSTimeoutException e) {
            throw new HSTimeoutException(HSMessageConstants.MSG_WS_CONEXAO_TIMEOUT);
        } catch (HSSocketException e) {
            throw new HSSocketException(HSMessageConstants.MSG_WS_SOCKET);
        } catch (HSProxyException e) {
            throw new HSProxyException(HSMessageConstants.MSG_WS_PROXY);
        } catch (Throwable e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
    }

    /**
     * Consulta a situação do recibo da NF-e.
     * <p><b>IMPORTANTE: Somente deverão constar no lote, NF-e's de mesmo tipo de emissão e da mesma versão.</b></p>
     * @param loteTO
     * @param certificadoTO 
     */
    public void consultarSituacaoRecibo(LoteTO loteTO, EmitenteTO emitenteTO, CertificadoTO certificadoTO) {
        try {
            boolean isSefaz = ScanHelper.isSefaz(loteTO);
            String wsReceptor = isSefaz ? HSMessageConstants.MSG_WS_SEFAZ : HSMessageConstants.MSG_WS_RFB;
            EstadoTO emitenteEstadoTO = UtilHelper.getInstance().getEstadoTOBySigla(emitenteTO.getEnderEmit().getUF());
            EstadoTO estadoTO = ScanHelper.getEstadoTO(emitenteEstadoTO, isSefaz);

            String buscaRetornoEnvioXml = DocumentHelper.createConsReciNFeDocument(loteTO.getNumeroRecibo());
            String buscaRetornoResultadoXml = getWSBusiness().servico(buscaRetornoEnvioXml, estadoTO, ServicoSefazEnum.RetRecepcao, certificadoTO);
            WSStatusRetornoEnum retornoEnum = DocumentHelper.validateRetConsReciNFeDocument(buscaRetornoEnvioXml, loteTO);
            switch(retornoEnum.ordinal()){
                case 2: //XML Inválido
                  loteTO.setCodigoRetorno(null);
                //notaFiscal.setMensagemErro(MessageFormat.format(HSMessageConstants.MSG_WS_RETORNO_INVALIDO, new Object[]{wsReceptor}));
            }
            
        } catch (HSBaseException ex) {
            loteTO.setException(ex);
        }
    }
}
